	title	'Customized Basic I/O System'

;*********************************************
;*                                           *
;* This Customized BIOS adapts CP/M-86 to    *
;* the following hardware configuration      *
;*     Processor:  8085/8088 Dual Processor  *
;*     Brand:  CompuPro (Godbout)            *
;*     Controller:  iCom 3712                *
;*                                           *
;*                                           *
;*     Programmer:  Bruce R. Ratoff          *
;*     Revisions :  04/30/81 20:40           *
;*                                           *
;*********************************************

true		equ -1
false		equ not true
cr		equ 0dh ;carriage return
lf		equ 0ah ;line feed

;*********************************************
;*                                           *
;* Loader_bios is true if assembling the     *
;* LOADER BIOS, otherwise BIOS is for the    *
;* CPM.SYS file.                             *
;*                                           *
;*********************************************

loader_bios	equ false
bdos_int	equ 224 ;reserved BDOS interrupt

;*********************************************
;*					     *
;*           I/O Port Assignments            *
;*					     *
;*********************************************
;
;Diskette interface (iCom 3712)
;Note: Port numbers are "doubled up" because iCom card
;	counts on 8080 "address mirror" effect.
datai	equ	0c0c0h	;data/status input port
datao	equ	0c1c1h	;data output port
cntrl	equ	0c0c0h	;command output port
;
;Console interface (IMSAI SIO2-2 port 1)
cstat	equ	3	;status
cdata	equ	2	;data
cimsk	equ	2	;input ready mask
comsk	equ	1	;output ready mask
;
;Printer interface (IMSAI SIO2-2 port 2)
lstat	equ	5	;status
ldata	equ	4	;data
lomsk	equ	1	;output ready mask
limsk	equ	2	;input ready mask


	IF	not loader_bios
;---------------------------------------------
;|                                           |
bios_code	equ 2500h
ccp_offset	equ 0000h
bdos_ofst	equ 0B06h ;BDOS entry point
;|                                           |
;---------------------------------------------
	ENDIF	;not loader_bios

	IF	loader_bios
;---------------------------------------------
;|                                           |
bios_code	equ 1200h ;start of LDBIOS
ccp_offset	equ 0003h ;base of CPMLOADER
bdos_ofst	equ 0406h ;stripped BDOS entry
;|                                           |
;---------------------------------------------
	ENDIF	;loader_bios

	cseg
	org	ccpoffset
ccp:
	org	bios_code

;*********************************************
;*                                           *
;* BIOS Jump Vector for Individual Routines  *
;*                                           *
;*********************************************

 jmp INIT	;Enter from BOOT ROM or LOADER
 jmp WBOOT	;Arrive here from BDOS call 0  
 jmp CONST	;return console keyboard status
 jmp CONIN	;return console keyboard char
 jmp CONOUT  	;write char to console device
 jmp LISTOUT	;write character to list device
 jmp PUNCH	;write character to punch device
 jmp READER  	;return char from reader device 
 jmp HOME	;move to trk 00 on cur sel drive
 jmp SELDSK  	;select disk for next rd/write
 jmp SETTRK  	;set track for next rd/write
 jmp SETSEC  	;set sector for next rd/write
 jmp SETDMA  	;set offset for user buff (DMA)
 jmp READ	;read a 128 byte sector
 jmp WRITE	;write a 128 byte sector
 jmp LISTST  	;return list status 
 jmp SECTRAN 	;xlate logical->physical sector 
 jmp SETDMAB 	;set seg base for buff (DMA)
 jmp GETSEGT 	;return offset of Mem Desc Table
 jmp GETIOBF	;return I/O map byte (IOBYTE)
 jmp SETIOBF	;set I/O map byte (IOBYTE) 

;*********************************************
;*                                           *
;* INIT Entry Point, Differs for LDBIOS and  *
;* BIOS, according to "Loader_Bios" value    *
;*                                           *
;*********************************************

INIT:	;print signon message and initialize hardware
	mov ax,cs	;we entered with a JMPF so use
	mov ss,ax	;CS: as the initial value of SS:,
	mov ds,ax	;DS:,
	mov es,ax	;and ES:
	;use local stack during initialization
	mov sp,offset stkbase
	cld		;set forward direction

	IF	not loader_bios
;---------------------------------------------
;|                                           |
	; This is a BIOS for the CPM.SYS file.
	; Setup all interrupt vectors in low
	; memory to address trap

	push ds		;save the DS register
	mov IOBYTE,0	;clear IOBYTE
	mov ax,0
	mov ds,ax
	mov es,ax 	;set ES and DS to zero
	;setup interrupt 0 to address trap routine
	mov int0_offset,offset int_trap
	mov int0_segment,CS
	mov di,4
	mov si,0	;then propagate
	mov cx,510	;trap vector to
	rep movs ax,ax	;all 256 interrupts
	;BDOS offset to proper interrupt
	mov bdos_offset,bdos_ofst
	mov int0_offset,offset int0_trap
	mov int4_offset,offset int4_trap
	pop ds		;restore the DS register

;	(additional CP/M-86 initialization)
;|                                           |
;---------------------------------------------
	ENDIF	;not loader_bios

	IF	loader_bios
;---------------------------------------------
;|                                           |
	;This is a BIOS for the LOADER
	push ds		;save data segment
	mov ax,0
	mov ds,ax	;point to segment zero
	;BDOS interrupt offset
	mov bdos_offset,bdos_ofst
	mov bdos_segment,CS ;bdos interrupt segment
;	(additional LOADER initialization)
	pop ds		;restore data segment
;|                                           |
;---------------------------------------------
	ENDIF	;loader_bios

	mov bx,offset signon
	call pmsg	;print signon message
	mov cl,0	;default to dr A: on coldstart
	jmp ccp		;jump to cold start entry of CCP

WBOOT:	jmp ccp+6	;direct entry to CCP at command level

	IF	not loader_bios
;---------------------------------------------
;|                                           |
int0_trap:
	cli
	mov bx,offset int0_trp
	jmps int_halt
int4_trap:
	cli
	mov bx,offset int4_trp
	jmps int_halt
int_trap:
	cli		;block interrupts
	mov bx,offset int_trp
int_halt:
	mov ax,cs
	mov ds,ax	;get our data segment
	call pmsg
	pop bx		;get offset
	pop ax		;print segment
	push bx		;save offset
	call PHEX
	mov cl,':'	;colon
	call CONOUT
	pop ax		;print offset
	call PHEX
	hlt		;hardstop

PHEX:
	push ax
	mov al,ah
	call PHXB	;print upper byte
	pop ax		;restore to print lower byte
PHXB:
	push ax		;save byte
	mov cl,4	;get high nibble
	shr al,cl	;into low bits
	call PHXD	;print digit
	pop ax		;restore byte
	and al,0fh	;isolate low nibble
PHXD:
	add al,90h	;first half of conversion trick
	daa
	add al,40h	;second half of same
	daa
	mov cl,al	;now print digit
	jmps CONOUT
;|                                           |
;---------------------------------------------
	ENDIF	;not loader_bios

;*********************************************
;*                                           *
;*   CP/M Character I/O Interface Routines   *
;*                                           *
;*********************************************

CONST:		;console status
	in	al,cstat	;get status byte
	and	al,cimsk	;check input mask
	jz	const1		;not ready yet...return al=0, ZF=1
	or	al,0ffh		;ready...return al=0FFh, ZF=0
CONST1:
	ret

CONIN:			;console input
	call CONST
	jz CONIN	;wait for RDA
	in	al,cdata;get byte
	and	al,7fh	;strip parity
	ret

CONOUT:		;console output
	in	al,cstat	;get status
	test	al,comsk	;check output bits
	jz	conout		;loop till ready
	mov	al,cl		;setup
	out	cdata,al	;send character
	ret		;then return data

LISTOUT:		;list device output
	call	LISTST	;get output status
	jz	LISTOUT	;wait for TBE
	mov	al,cl	;setup
	out	ldata,al	;send char
	in	al,lstat	;check for handshake received
	and	al,limsk
	jz	LISTOUT2	;no handshake...exit
	in	al,ldata	;get handshake char
	and	al,7fh		;strip parity
	cmp	al,'S'-40h	;XOFF?
	jnz	LISTOUT2	;nope
	mov	lstactive,0ffh	;set list active flag
LISTOUT2:
	ret

LISTST:			;poll list status
	in	al,lstat	;get status byte
	and	al,lomsk	;test output bits
	jz	LISTST1		;not ready...exit with al=0, zf=1
	mov	al,lstactive	;line ready...waiting for XON?
	not	al
	test	al,al
	jnz	LISTST1		;not waiting...say ready
	in	al,lstat	;check for handshake
	and	al,limsk
	jz	LISTST1		;not yet...say still busy
	in	al,ldata	;got something...
	and	al,7fh		;strip parity
	cmp	al,'Q'-40h	;is it XON?
	mov	al,0
	jnz	LISTST1		;no, return false
	not	al		;ready...exit with al=0ffh, zf=0
	mov	lstactive,0	;clear list active flag
LISTST1:
	test	al,al		;make sure flags are set
	ret

PUNCH:		;write punch device
	ret	;is a "bit bucket"

READER:
	mov	al,1ah	;is an EOF source
	ret

GETIOBF:
	mov al,IOBYTE
	ret

SETIOBF:
	mov IOBYTE,cl	;set iobyte
	ret		;iobyte not implemented

pmsg:
	mov al,[BX] 	;get next char from message
	test al,al
	jz return	;if zero return
	mov CL,AL
	call CONOUT  	;print it
	inc BX
	jmps pmsg	;next character and loop

;*********************************************
;*                                           *
;*          Disk Input/Output Routines       *
;*                                           *
;*********************************************

SELDSK:		;select disk given by register CL
ndisks	equ	2 ;number of disks (up to 16)
	mov seekfg,0ffh	;set seek flag
	mov disk,cl	;save disk number
	mov bx,0000h	;ready for error return
	cmp cl,ndisks	;n beyond max disks?
	jnb return	;return if so
	mov ch,0	;double(n)
	mov bx,cx	;bx = n
	mov cl,4	;ready for *16
	shl bx,cl	;n = n * 16
	mov cx,offset dpbase
	add bx,cx	;dpbase + n * 16
return:	ret		;bx = .dph

HOME:	;move selected disk to home position (Track 0)
	mov cx,0	;set disk i/o to track zero
			;**** fall through ****
SETTRK: ;set track address given by CX
	mov trk,CX
	mov seekfg,0ffh	;set seek flag
	ret

SETSEC: ;set sector number given by cx
	mov sect,CX
	ret

SECTRAN: ;translate sector CX using table at [DX]
	mov bx,cx
	add bx,dx	;add sector to tran table address
	mov bl,[bx]	;get logical sector
	ret

SETDMA: ;set DMA offset given by CX
	mov dma_adr,CX
	ret

SETDMAB: ;set DMA segment given by CX
	mov dma_seg,CX
	ret
;
GETSEGT:  ;return address of physical memory table
	mov bx,offset seg_table
	ret

;*********************************************
;*                                           *
;*  All disk I/O parameters are setup:       *
;*     DISK     is disk number      (SELDSK) *
;*     TRK      is track number     (SETTRK) *
;*     SECT     is sector number    (SETSEC) *
;*     DMA_ADR  is the DMA offset   (SETDMA) *
;*     DMA_SEG  is the DMA segment  (SETDMAB)*
;*  READ reads the selected sector to the DMA*
;*  address, and WRITE writes the data from  *
;*  the DMA address to the selected sector   *
;*  (return 00 if successful,  01 if perm err)*
;*                                           *
;*********************************************

READ:
	mov cl,10	;set retry count
READ1:
	call STUP	;set up unit/track/sector
	mov al,3	;send read command
	call DLOOP
	mov dx,datai	;set port number
	in al,dx	;get back status
	test al,8	;check CRC flag
	jz RDOK		;no error...go get data
	dec cl		;got an error...count retrys
	jnz READ2	;some retrys left...continue
	mov al,1	;bad news....return error
	ret
READ2:
	test cl,3	;time for a re-seek?
	jpo READ1	;no, just reread
	mov seekfg,0ffh	;yes, set seek flag
	call RESET	;clear errors, home drive
	jmps READ1	;try read again
RDOK:
	mov cx,128	;set byte counter
	cld		;set forward direction
	push es		;save extra segment
	les di,dword ptr dma_adr	;set dest index and segment
	mov dx,cntrl
RDLUP:
	mov ax,40h	;send "examine read buffer" command
	out dx,al	;to disk control port
	in al,dx	;get data byte
	stos al		;store it, bump pointer and count
	mov al,41h	;send "step read buffer" command
	out dx,al	;to controller
	loop RDLUP	;repeat 128 times
	pop es		;restore extra segment
	mov al,0	;return good status
	out dx,al	;also put controller in status mode
	ret

WRITE:
	mov cx,128	;set 128 byte counter
	cld		;set forward direction
	push ds		;save current data segment
	lds si,dword ptr dma_adr	;set source index
WRLUP:
	lods al		;get next byte
	mov dx,datao
	out dx,al	;send to controller
	mov al,31h	;send "shift write buffer" command
	mov dx,cntrl
	out dx,al	;to controller
	mov al,0	;remove command
	out dx,al	;(bit 0 must toggle to be seen)
	loop WRLUP	;repeat for sector length times
	pop ds
RTRYP:
	call STUP	;setup for write
	in al,dx	;check controller status
	test al,10h	;write protected?
	jz TRYWR	;no, continue
	mov bx,offset prtmsg	;say "protected"
	call ERROR	;and wait for user action
	jmps RTRYP	;retry if user hits return key
TRYWR:
	mov al,5	;send write command
	call DLOOP	;to controller with wait
WROK:
	mov al,0	;return good status
	ret

;*********************************************
;*					     *
;*	      Disk Utility Routines	     *
;*					     *
;*********************************************
;
;print an error message and wait for user response
;if control-c, then abort to cp/m, else return
;to caller and (usually) retry operation
ERROR:
	call PMSG	;print an error message
	call CONIN	;wait for user response
	push ax		;save character
	mov bx,offset crlf	;echo cr, lf
	call PMSG
	pop ax		;now look at char
	cmp al,3	;control-c?
	jz ERR1		;yes, return to cp/m
	ret		;else retry error'd operation
ERR1:
	mov cl,0	;tell cp/m user 0, drive A
	jmp ccp		;bye-bye

;Perform select and possibly seek logic for either a
;read or write operation.
STUP:
	mov al,0bh	;issue "reset errors" command
	call DLOOP	;to controller with wait
	mov al,disk	;get drive number
	mov cl,6	;prepare to shift into
	shl ax,cl	;high 2 bits of cmd byte
	or ax,sect	;put sector number in low bits
	mov dx,datao
	out dx,al	;send to controller
	mov al,21h	;issue "set unit/sector" command
	call DLOOP
	mov dx,datai
	mov cx,100	;set up delay loop
STUP0:
	mov bx,8000	;inner delay loop
STUP1:
	in al,dx	;get controller status
	test al,20h	;check "drive fail" (ready) flag
	jz STUP2	;no problem...continue
	dec bx		;count down inner delay loop
	jnz STUP1
	loop STUP0	;count down outer delay loop
	mov bx,offset rdymsg	;timed out...complain
	call error	;and wait for response
	jmps STUP	;retry the whole mess
STUP2:
	mov al,0	;clear seek flag
	xchg al,seekfg	;and fetch previous value
	test al,al	;was it set?
	jnz stup3	;yes, go do seek or home
	ret		;no seek needed...exit
STUP3:
	mov ax,trk	;look at track number
	test al,al	;is it 0?
	jz RESET	;yes, do a home
	mov dx,datao
	out dx,al	;otherwise, set new track
	mov al,11h	;give "set track" command
	call DLOOP
	mov al,9	;then give "seek" command
			;**** fall through ****
;
;This routine issues a controller command and waits for completion
DLOOP:
	mov dx,cntrl
	out dx,al	;send command
	mov al,0	;strobe it off
	out dx,al
LOOP1:
	in al,dx	;get controller status
	test al,1	;check ready bit
	jnz LOOP1	;loop till ready
	ret		;then exit
;
;This routine issues a "clear" command followed by a "home" command
RESET:
	mov al,81h	;send "clear"
	call DLOOP
	mov al,0dh	;send "home"
	jmps DLOOP


;*********************************************
;*                                           *
;*               Data Areas                  *
;*                                           *
;*********************************************
data_offset	equ offset $

	dseg
	org	data_offset	;contiguous with code segment
lstactive	db	0	;set if list handshake active
IOBYTE	db	0	;i/o assignments (unused at present)
seekfg	db	0	;set to 0ffh if next access requires seek
disk	db	0	;disk number
trk	dw	0	;track number
sect	dw	0	;sector number
dma_adr	dw	0	;DMA offset from DS
dma_seg	dw	0	;DMA Base Segment

signon 	db	cr,lf,cr,lf
	db	'CP/M-86 Version 1.0 for iCom 3712',cr,lf
	db	'System Generated 04/30/81'
	db	cr,lf,0

int_trp	db	cr,lf
	db	'Interrupt Trap Halt at ',0

int0_trp db	cr,lf
	db	'Divide Trap Halt at ',0

int4_trp db	cr,lf
	db	'Overflow Trap Halt at ',0

rdymsg	db	cr,lf
	db	'Drive not ready',0

prtmsg	db	cr,lf
	db	'Drive write protected',0

crlf	db	cr,lf,0

;	System Memory Segment Table

segtable db 1	;1 segment
	dw tpa_seg	;1st seg starts after BIOS
	dw tpa_len	;and extends to 0ffff

	include singles.lib ;read in disk definitions

loc_stk	rw  32	;local stack for initialization
stkbase	equ offset $

lastoff	equ offset $
tpa_seg	equ (lastoff+0400h+15) / 16
tpa_len	equ 0fffh - tpa_seg	; 64K less 16 byte reset vector less cp/m size

	db 0	;fill last address for GENCMD

;*********************************************
;*                                           *
;*          Dummy Data Section               *
;*                                           *
;*********************************************
	dseg	0	;absolute low memory
	org 	0	;(interrupt vectors)
int0_offset	rw	1
int0_segment	rw	1
;	pad to overflow trap vector
		rw	6
int4_offset	rw	1
int4_segment	rw	1
;	pad to system call vector
	rw	2*(bdos_int-5)

bdos_offset	rw	1
bdos_segment	rw	1
	END
